import time
import traceback
from datetime import datetime
from threading import Thread
from typing import Optional
from loguru import logger
from paho.mqtt import client as mqtt
import json

global ClientMap

ClientMap: dict[str, "MqttClient"] = {}


def getMqttClient(clientName):
    if ClientMap.get(clientName):
        return ClientMap[clientName]
    else:
        return None


class MqttClient(Thread):
    def __init__(
        self,
        name: str,
        host: str,
        port: int,
        clientId: Optional[str] = None,
        username: Optional[str] = None,
        password: Optional[str] = None,
        willTopic: Optional[str] = None,
        willPayload: Optional[str] = None,
        willQoS: int = 0,
        willRetain: bool = False,
        keepAlive: int = 60,
    ):
        super().__init__(daemon=True)
        self.__name = name
        self.__host = host
        self.__port = port
        self.__keepAlive = keepAlive
        self.client = mqtt.Client(client_id=clientId)
        self.subscribes = []
        self.logs: list = []
        self.client.on_connect = self.onConnect
        self.client.on_message = self.onMessage
        self.client.on_disconnect = self.onDisconnect
        if username and password:
            self.client.username_pw_set(username=username, password=password)
        if willTopic and willPayload:
            self.client.will_set(topic=willPayload, payload=willPayload, qos=willQoS, retain=willRetain)

    @property
    def name(self):
        return self.__name

    def addLog(self, info: str):
        while len(self.logs) > 10:
            self.logs.pop(0)
        self.logs.append({
            "time": datetime.now(),
            "content": info
        })

    def onConnect(self, client: mqtt.Client, userdata, flags, rc):
        for sub in self.subscribes:
            client.subscribe(sub)
    
    def onMessage(self, client, userdata, msg):
        pass

    def onDisconnect(self, client, userdata, rc):
        print("disconnect...")
        pass
    
    def run(self):
        start = False
        while not start:
            try:
                self.client.connect(host=self.__host, port=self.__port, keepalive=self.__keepAlive)
                start = True
            except Exception as e:
                logger.info(e)
                time.sleep(2)
        self.client.loop_forever(retry_first_connection=True)

    def send(self, topic, payload, qos=0, retain=False):
        # if self.client.is_connected:
        _payload = payload
        if isinstance(payload, dict):
            _payload = json.dumps(payload)
        res = self.client.publish(topic=topic, payload=_payload, qos=qos, retain=retain)
        try:
            return res.is_published()
        except Exception as e:
            return e

def createMqttClient(
        name: str, clientId: Optional[str], 
        host: str, port: int, 
        username: Optional[str] = None,
        password: Optional[str] = None,
        willTopic: Optional[str] = None,
        willPayload: Optional[str] = None,
        willQoS: int = 0,
        willRetain: bool = False,
        keepAlive: int = 60
    ):
    cli = MqttClient(
        name=name, clientId=clientId, 
        host=host, port=port, 
        keepAlive=keepAlive,
        username=username, password=password,
        willTopic=willTopic, willPayload=willPayload,
        willQoS=willQoS, willRetain=willRetain
    )
    ClientMap[name] = cli
    cli.start()
